#include "IHD44780.h"
#include "HD44780AnalyzerSettings.h"

HD44780AnalyzerSettings*                IHD44780::mSettings = NULL;
std::vector< SimulationChannelDescriptor* >*    IHD44780::mData = NULL;
SimulationChannelDescriptorGroup*               IHD44780::mSimulationData = NULL;
ClockGenerator                                  IHD44780::mClockGenerator;


const double IHD44780::TIME_AS     = 40;
const double IHD44780::TIME_ER     = 20;
const double IHD44780::TIME_PWEH   = 230;
const double IHD44780::TIME_H      = 10;
const double IHD44780::TIME_YCE    = 500;
const double IHD44780::TIME_DDR    = 160;
const double TARGET_FREQUENCY = 16000000;

void IHD44780::Init(  U32  SimulationSampleRateHz ,
    HD44780AnalyzerSettings* Settings , 
    std::vector< SimulationChannelDescriptor* >* Data, SimulationChannelDescriptorGroup* SimulationData )
{
    if (NULL == Settings || NULL == Data || NULL == SimulationData )
    {    }
    mSettings = Settings;
    mData = Data;
    mSimulationData = SimulationData;
    mClockGenerator.Init( TARGET_FREQUENCY, SimulationSampleRateHz ); 
}

void IHD44780::NoOperation()
{
SendCommand( CMD_NOOP );
}

void IHD44780::ClearDisplay()
{
   SendCommand( CMD_CLEARDISPLAY );
}

void IHD44780::ReturnHome()
{
   SendCommand( CMD_RETURNHOME );
}

void IHD44780::EntryMode( MoveDirection CursorMode, DisplayShift DisplayMode)
{
    SendCommand( CMD_ENTRYMODE | CursorMode | DisplayMode );
}

void IHD44780::Display( DisplayState Display, CursorState Cursor, BlinkingState Blink )
{
    SendCommand( CMD_DISPLAYCONTROL | Display | Cursor | Blink );
}

void IHD44780::Shift( ScreenShiftState ShiftState, ShiftDirection Direction )
{
    SendCommand( CMD_SHIFT | ShiftState | Direction  );
}

void IHD44780::Mode( DataLength Length, DataLines Lines, ScreenFont Font )
{
    SendCommand( CMD_FUNCTION | Length | Lines | Font );
}

void IHD44780::SetCGRAMAddress( unsigned char Address )
{
    SendCommand( CMD_CGRAM | Address );
}

void IHD44780::SetDDRAMAddress( unsigned char Address )
{
    SendCommand( CMD_DDRAM | Address );
}

void IHD44780::CheckBusyFlagAsDelay()
{
   WaitInMicros(40);
}

void IHD44780::WaitInMillis( double TimeToWait )
{
    mSimulationData->AdvanceAll( mClockGenerator.AdvanceByTimeS( TimeToWait/1000) );
}

void IHD44780::WaitInMicros( double TimeToWait )
{
    mSimulationData->AdvanceAll( mClockGenerator.AdvanceByTimeS( TimeToWait/1000000) );
}

void IHD44780::WaitInNanos( double TimeToWait )
{
    mSimulationData->AdvanceAll( mClockGenerator.AdvanceByTimeS( TimeToWait/1000000000) );
}

void IHD44780::WriteString( char* Data )
{
    while( *Data )
        SendData( *Data++);
}

void IHD44780::SetControlForData()
{
    (*mData)[ mSettings->GetRs() ]->TransitionIfNeeded( BIT_HIGH );
    (*mData)[ mSettings->GetRw() ]->TransitionIfNeeded( BIT_LOW );
    (*mData)[ mSettings->GetRe() ]->TransitionIfNeeded( BIT_LOW );
}

void IHD44780::SetControlForCommand()
{
    (*mData)[ mSettings->GetRs() ]->TransitionIfNeeded( BIT_LOW );
    (*mData)[ mSettings->GetRw() ]->TransitionIfNeeded( BIT_LOW );
    (*mData)[ mSettings->GetRe() ]->TransitionIfNeeded( BIT_LOW );
}

void IHD44780::SetControlForBusyFlag()
{
    (*mData)[ mSettings->GetRs() ]->TransitionIfNeeded( BIT_LOW );
    (*mData)[ mSettings->GetRw() ]->TransitionIfNeeded( BIT_HIGH );
    (*mData)[ mSettings->GetRe() ]->TransitionIfNeeded( BIT_LOW );
}

void IHD44780::SetControlLow()
{
    (*mData)[ mSettings->GetRs() ]->TransitionIfNeeded( BIT_LOW );
    (*mData)[ mSettings->GetRw() ]->TransitionIfNeeded( BIT_LOW );
    (*mData)[ mSettings->GetRe() ]->TransitionIfNeeded( BIT_LOW );
}

void IHD44780::PulseEnableLine()
{
    (*mData)[ mSettings->GetRe()]->TransitionIfNeeded( BIT_HIGH );
    WaitInNanos( TIME_ER + TIME_PWEH ); 
    (*mData)[ mSettings->GetRe()]->TransitionIfNeeded( BIT_LOW );
    WaitInNanos( TIME_H );  
}

void  IHD44780::SendCommand( U8 Command )
{
    SetControlForCommand();
    SetDataLines(Command);
    WaitInNanos( TIME_AS );
    PulseEnableLine();
    SetControlLow();
    SetDataLines(0);
    WaitInNanos( TIME_YCE - (TIME_AS + TIME_PWEH + TIME_ER + TIME_H) ); 
}

void IHD44780::SendData( U8 Data )
{
    SetControlForData();
    SetDataLines(Data);
    WaitInNanos( TIME_AS );
    PulseEnableLine();
    SetControlLow();
    SetDataLines(0);
    WaitInNanos( TIME_YCE - (TIME_AS + TIME_PWEH + TIME_ER + TIME_H) ); 
    CheckBusyFlagAsPulse();
}


void IHD44780::CheckBusyFlagAsLoop()
{
    SetControlForBusyFlag();
    WaitInNanos( TIME_AS );

    (*mData)[ mSettings->GetRe()]->TransitionIfNeeded( BIT_HIGH );
    WaitInNanos( TIME_DDR );

    SetDataLines( 0x80 );
    WaitInMicros(40);

    (*mData)[ mSettings->GetData7() ]->TransitionIfNeeded( BIT_LOW  );
    WaitInMicros(1);

    SetControlLow();

    SetDataLines(0);
    WaitInNanos(200);
}


// TODO: Fix lag in bit 7 when initialization starts.  BIT 7 should go high when the control is set for buzy flag.
void IHD44780::CheckBusyFlagAsPulse()
{
    for ( int i = 0; i < 2; ++i )
    {
        SetControlForBusyFlag();
        WaitInNanos( TIME_AS );

        (*mData)[ mSettings->GetRe()]->TransitionIfNeeded( BIT_HIGH );
        WaitInNanos( TIME_DDR );

        SetDataLines( i == 0 ? 0x80 : 0x00 );
        WaitInNanos(800);

        SetControlLow();
        SetDataLines(0);
        WaitInNanos(200);
    }
}